package plus.easydo.starter.log.aspect;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import plus.easydo.starter.log.configuration.SystemLogAutoConfiguration;
import plus.easydo.starter.log.model.SystemLog;
import plus.easydo.starter.log.server.DefaultLogStoreServer;
import plus.easydo.starter.log.server.DefaultSystemLogUserServer;
import plus.easydo.starter.log.server.SystemLogUserServer;
import plus.easydo.utils.IpUtil;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.sql.Timestamp;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;


/**
 * 日志记录打印、记录切面
 *
 * @author yuzhanfeng
 */
@Aspect
@Component
@Import(SystemLogAutoConfiguration.class)
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SystemLogAspect {

    private static final Logger logger = LoggerFactory.getLogger(SystemLogAspect.class);

    @Autowired(required = false)
    SystemLogUserServer userServer = new DefaultSystemLogUserServer();

    @Autowired
    private DefaultLogStoreServer logServer;


    /**
     * 切入点
     *
     * @author laoyu
     */
    @Pointcut("within(plus.easydo.starter.log.annotation.SystemLog) " +
              "|| @target(plus.easydo.starter.log.annotation.SystemLog) " +
              "|| @annotation(plus.easydo.starter.log.annotation.SystemLog)")
    public void pointcut() {
        // add pointcut
    }



    /**
     * 日志环绕切面
     *
     * @param point point
     * @param systemLog systemLog
     * @return java.lang.Object
     * @author laoyu
     */
    @Around("pointcut() && @annotation(systemLog)")
    public Object around(ProceedingJoinPoint point, plus.easydo.starter.log.annotation.SystemLog systemLog) throws Throwable {
        long startTime = System.currentTimeMillis();
        String moduleName = systemLog.value();
        Object result = null;
        String exceptionInfo = null;
        SystemLog log = new SystemLog();
        log.setModuleName(moduleName);
        log.setBeginTime(new Timestamp(startTime));
        try {
            result = point.proceed();
            return result;
        } catch (Exception exception){
            StringWriter stringWriter = new StringWriter();
            PrintWriter printWriter = new PrintWriter(stringWriter);
            exception.printStackTrace(printWriter);
            exceptionInfo = exception + "/n message:" + exception.getMessage() + "/n" + stringWriter;
            throw exception;
        }finally {
            logger.info("LogServer print -------------------------------------------start-----------------------------------------------");
            logger.info("   moduleName ==> {}", moduleName);
            printRequest(systemLog,point,log);
            printMethod(systemLog,point,result,log);
            printResponse(systemLog,log);
            logger.info("  exception ==> {}", exceptionInfo);
            long endTime = System.currentTimeMillis();
            long execTime = endTime - startTime;
            if (systemLog.saveLog()) {
                //save log
                String userId = userServer.getUserId();
                String userName = userServer.getUserName();
                String finalExceptionInfo = exceptionInfo;
                CompletableFuture.supplyAsync(() -> {
                log.setOptionUserId(userId);
                log.setOptionUserName(userName);
                log.setExecTime(execTime);
                log.setExceptionInfo(finalExceptionInfo);
                return logServer.save(log);
                });
            }
            logger.info("   execTime ==> {}", execTime);
            logger.info("LogServer print --------------------------------------------end-----------------------------------------------");
        }
    }

    /**
     * 功能描述
     *
     * @param systemLog systemLog
     * @param log log
     * @author laoyu
     */
    private void printResponse(plus.easydo.starter.log.annotation.SystemLog systemLog, SystemLog log) {
        HttpServletResponse response = getResponse();
        if(Objects.nonNull(response)){
            int status = response.getStatus();
            log.setResponseStatus(String.valueOf(status));
            //print response
            if (systemLog.printLog() && systemLog.printResponse()) {
                int size = response.getBufferSize();
                logger.info("   status ==> {}", status);
                logger.info("   size ==> {}", size);
            }
        }
    }


    /**
     * 打印请求参数
     *
     * @param systemLog systemLog
     * @param point point
     * @param log log
     * @author laoyu
     */
    public void printRequest(plus.easydo.starter.log.annotation.SystemLog systemLog, ProceedingJoinPoint point, SystemLog log) {
        String params;
        long requestId = IdUtil.getSnowflake().nextId();
        HttpServletRequest request = getRequest();
        log.setRequestId(requestId);
        String ip = IpUtil.getIpAddress(request);
        String path = request.getRequestURI();
        String requestMethod = request.getMethod();
        String queryString = request.getQueryString();
        String url = request.getRequestURL().toString();
        String sign = request.getHeader("user-agent");
        Map<String, String[]> parameterMap = request.getParameterMap();
        params = MapUtil.isEmpty(parameterMap)? JSONUtil.toJsonStr(point.getArgs()):JSONUtil.toJsonStr(parameterMap);
        request.setAttribute("logServerRequestId", requestId);
        log.setRequestIp(ip);
        log.setRequestSign(sign);
        log.setRequestPath(path);
        log.setRequestUri(url + ((CharSequenceUtil.isBlank(queryString) || "null".equals(queryString)) ? "" : queryString));
        log.setRequestParams(params);
        log.setRequestMethod(requestMethod);
        //print request
        if (systemLog.printLog() && systemLog.printRequest()) {
            int port = request.getRemotePort();
            String host = request.getRemoteHost();
            logger.info("   requestMethod ==> {}", requestMethod);
            logger.info("   IP ==> {}", ip);
            logger.info("   host ==> {}", host);
            logger.info("   port ==> {}", port);
            logger.info("   path ==> {}", path);
            logger.info("   uri ==> {}", url);
            logger.info("   sign ==> {}", sign);
            logger.info("   request params ==> {}", params);
        }
    }

    /**
     * 打印方法参数
     *
     * @param systemLog systemLog
     * @param point point
     * @param result result
     * @param log log
     * @author laoyu
     */
    private void printMethod(plus.easydo.starter.log.annotation.SystemLog systemLog, ProceedingJoinPoint point, Object result, SystemLog log) {
        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        Object[] args = point.getArgs();
        String methodName = method.getName();
        Type[] parameterTypes = method.getGenericParameterTypes();
        String className = method.getDeclaringClass().getSimpleName();
        String resultJson = JSONUtil.toJsonStr(result);
        log.setClassName(className);
        log.setMethodName(methodName);
        log.setMethodParams(JSONUtil.toJsonStr(args));
        log.setMethodReturn(resultJson);
        //print param
        if (systemLog.printLog() && systemLog.printMethod()) {
            logger.info("   class name ==> {}", className);
            logger.info("   method name ==> {}", methodName);
            logger.info("   method param type ==> {}", parameterTypes);
            logger.info("   method params ==> {}", args);
            logger.info("   method return type ==> {}", Objects.nonNull(result) ? result.getClass().getTypeName() : null);
            logger.info("   method return value ==> {}", resultJson);
        }
    }

    /**
     * 获得当前请求的request对象
     *
     * @return javax.servlet.http.HttpServletRequest
     * @author laoyu
     */
    private HttpServletRequest getRequest() {
        try {
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            return requestAttributes.getRequest();
        }catch (Exception e){
            logger.info("   getRequestErr ==> {}", e.getMessage());
            return null;
        }
    }

    /**
     * 获得响应
     *
     * @return javax.servlet.http.HttpServletResponse
     * @author laoyu
     */
    private HttpServletResponse getResponse(){
        try {
            ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            return servletRequestAttributes.getResponse();
        }catch (Exception e){
            logger.info("   getResponseErr ==> {}", e.getMessage());
            return null;
        }
    }




}
